import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../../dgg_flutter_date_picker.dart';

/// Solar months of 31 days.
const List<int> _solarMonthsOf31Days = const <int>[1, 3, 5, 7, 8, 10, 12];

/// DatePicker widget.
///
/// @author dylan wu
/// @since 2019-05-10
class DatePeriodPickerWidget extends StatefulWidget {
  DatePeriodPickerWidget({
    Key key,
    this.minDateTime,
    this.maxDateTime,
    this.initialDateTime,
    this.dateFormat: DATETIME_PICKER_DATE_FORMAT,
    this.pickerTheme: DatePickerTheme.Default,
    this.onCancel,
    this.onChange,
    this.onConfirm,
  }) : super(key: key) {
    DateTime minTime = minDateTime ?? DateTime.parse(DATE_PICKER_MIN_DATETIME);
    DateTime maxTime = maxDateTime ?? DateTime.parse(DATE_PICKER_MAX_DATETIME);
    assert(minTime.compareTo(maxTime) < 0);
  }

  final DateTime minDateTime, maxDateTime, initialDateTime;
  final String dateFormat;
  final DatePickerTheme pickerTheme;

  final DateVoidCallback onCancel;
  final DatePeriodValueCallback onChange, onConfirm;

  @override
  State<StatefulWidget> createState() => _DatePickerWidgetState(
      this.minDateTime, this.maxDateTime, this.initialDateTime);
}

class _DatePickerWidgetState extends State<DatePeriodPickerWidget> {
  DateTime _minDateTime, _maxDateTime;
  int _currYearLeft, _currMonthLeft, _currDayLeft,_currYearRight, _currMonthRight, _currDayRight;
  List<int> _yearRangeLeft, _monthRangeLeft, _dayRangeLeft,_yearRangeRight, _monthRangeRight, _dayRangeRight;

  FixedExtentScrollController _yearScrollCtrlLeft, _monthScrollCtrlLeft, _dayScrollCtrlLeft;
  FixedExtentScrollController _yearScrollCtrlRight, _monthScrollCtrlRight, _dayScrollCtrlRight;

  Map<String, FixedExtentScrollController> _scrollCtrlMapLeft,_scrollCtrlMapRight;
  Map<String, List<int>> _valueRangeMapLeft,_valueRangeMapRight;

  bool _isChangeDateRangeLeft = false;
  bool _isChangeDateRangeRight = false;

  _DatePickerWidgetState(
      DateTime minDateTime, DateTime maxDateTime, DateTime initialDateTime) {
    // handle current selected year、month、day
    DateTime initDateTime = initialDateTime ?? DateTime.now();
    this._currYearLeft = initDateTime.year;
    this._currMonthLeft = initDateTime.month;
    this._currDayLeft = initDateTime.day;
    this._currYearRight = initDateTime.year;
    this._currMonthRight = initDateTime.month;
    this._currDayRight = initDateTime.day;

    // handle DateTime range
    this._minDateTime = minDateTime ?? DateTime.parse(DATE_PICKER_MIN_DATETIME);
    this._maxDateTime = maxDateTime ?? DateTime.parse(DATE_PICKER_MAX_DATETIME);

    // limit the range of year
    this._yearRangeLeft = _calcYearRange();
    this._currYearLeft = min(max(_minDateTime.year, _currYearLeft), _maxDateTime.year);

    // limit the range of month
    this._monthRangeLeft = _calcMonthRangeLeft();
    this._currMonthLeft = min(max(_monthRangeLeft.first, _currMonthLeft), _monthRangeLeft.last);

    // limit the range of day
    this._dayRangeLeft = _calcDayRangeLeft();
    this._currDayLeft = min(max(_dayRangeLeft.first, _currDayLeft), _dayRangeLeft.last);

    // limit the range of year
    this._yearRangeRight = _calcYearRange();
    this._currYearRight = min(max(_minDateTime.year, _currYearRight), _maxDateTime.year);

    // limit the range of month
    this._monthRangeRight = _calcMonthRangeRight();
    this._currMonthRight = min(max(_monthRangeRight.first, _currMonthRight), _monthRangeRight.last);

    // limit the range of day
    this._dayRangeRight = _calcDayRangeRight();
    this._currDayRight = min(max(_dayRangeRight.first, _currDayRight), _dayRangeRight.last);

    // create scroll controller
    _yearScrollCtrlLeft =
        FixedExtentScrollController(initialItem: _currYearLeft - _yearRangeLeft.first);
    _monthScrollCtrlLeft = FixedExtentScrollController(
        initialItem: _currMonthLeft - _monthRangeLeft.first);
    _dayScrollCtrlLeft=
        FixedExtentScrollController(initialItem: _currDayLeft - _dayRangeLeft.first);
    // create scroll controller
    _yearScrollCtrlRight =
        FixedExtentScrollController(initialItem: _currYearRight - _yearRangeRight.first);
    _monthScrollCtrlRight = FixedExtentScrollController(
        initialItem: _currMonthRight - _monthRangeRight.first);
    _dayScrollCtrlRight =
        FixedExtentScrollController(initialItem: _currDayRight - _dayRangeRight.first);
    _scrollCtrlMapLeft = {
      'y': _yearScrollCtrlLeft,
      'M': _monthScrollCtrlLeft,
      'd': _dayScrollCtrlLeft
    };
    _scrollCtrlMapRight = {
      'y': _yearScrollCtrlRight,
      'M': _monthScrollCtrlRight,
      'd': _dayScrollCtrlRight
    };
    _valueRangeMapLeft = {'y': _yearRangeLeft, 'M': _monthRangeLeft, 'd': _dayRangeLeft};
    _valueRangeMapRight = {'y': _yearRangeRight, 'M': _monthRangeRight, 'd': _dayRangeRight};
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Material(
          color: Colors.transparent, child: _renderPickerView(context)),
    );
  }

  /// render date picker widgets
  Widget _renderPickerView(BuildContext context) {
    Widget datePickerWidget = _renderDatePickerWidget();

    // display the title widget
    if (widget.pickerTheme.title != null || widget.pickerTheme.showTitle) {
      Widget titleWidget = DateTitleWidget(
        pickerTheme: widget.pickerTheme,
        onCancel: () => _onPressedCancel(),
        onConfirm: () => _onPressedConfirm(),
      );
      return Column(children: <Widget>[titleWidget, datePickerWidget]);
    }
    return datePickerWidget;
  }

  /// pressed cancel widget
  void _onPressedCancel() {
    if (widget.onCancel != null) {
      widget.onCancel();
    }
    Navigator.pop(context);
  }

  /// pressed confirm widget
  void _onPressedConfirm() {
    if (widget.onConfirm != null) {
      DateTime dateTimeLeft = DateTime(_currYearLeft, _currMonthLeft, _currDayLeft);
      DateTime dateTimeRight = DateTime(_currYearRight, _currMonthRight, _currDayRight);
      widget.onConfirm(dateTimeLeft, _calcSelectIndexListLeft(),dateTimeRight, _calcSelectIndexListRight());
    }
    Navigator.pop(context);
  }

  /// notify selected date changed
  void _onSelectedChange() {
    if (widget.onChange != null) {
      DateTime dateTimeLeft = DateTime(_currYearLeft, _currMonthLeft, _currDayLeft);
      DateTime dateTimeRight = DateTime(_currYearRight, _currMonthRight, _currDayRight);
      widget.onConfirm(dateTimeLeft, _calcSelectIndexListLeft(),dateTimeRight, _calcSelectIndexListRight());
    }
  }

  /// find scroll controller by specified format
  FixedExtentScrollController _findScrollCtrlLeft(String format) {
    FixedExtentScrollController scrollCtrl;
    _scrollCtrlMapLeft.forEach((key, value) {
      if (format.contains(key)) {
        scrollCtrl = value;
      }
    });
    return scrollCtrl;
  }
  /// find scroll controller by specified format
  FixedExtentScrollController _findScrollCtrlRight(String format) {
    FixedExtentScrollController scrollCtrl;
    _scrollCtrlMapRight.forEach((key, value) {
      if (format.contains(key)) {
        scrollCtrl = value;
      }
    });
    return scrollCtrl;
  }

  /// find item value range by specified format
  List<int> _findPickerItemRangeLeft(String format) {
    List<int> valueRange;
    _valueRangeMapLeft.forEach((key, value) {
      if (format.contains(key)) {
        valueRange = value;
      }
    });
    return valueRange;
  }
  /// find item value range by specified format
  List<int> _findPickerItemRangeRight(String format) {
    List<int> valueRange;
    _valueRangeMapRight.forEach((key, value) {
      if (format.contains(key)) {
        valueRange = value;
      }
    });
    return valueRange;
  }

  /// render the picker widget of year、month and day
  Widget _renderDatePickerWidget() {
    List<Widget> pickers = List<Widget>();
    List<String> formatArr =
    DateFormatter.splitDateFormat(widget.dateFormat);
    formatArr.forEach((format) {
      List<int> valueRange = _findPickerItemRangeLeft(format);

      Widget pickerColumn = _renderDatePickerColumnComponent(
        scrollCtrl: _findScrollCtrlLeft(format),
        valueRange: valueRange,
        format: format,
        valueChanged: (value) {
          if (format.contains('y')) {
            _changeYearSelectionLeft(value);
          } else if (format.contains('M')) {
            _changeMonthSelectionLeft(value);
          } else if (format.contains('d')) {
            _changeDaySelectionLeft(value);
          }
        },
      );
      pickers.add(pickerColumn);
    });
    TextStyle textStyle = TextStyle(
            color: Color(0xff333333), fontSize: 16.0);
    pickers.add(Container(
      color: Colors.white,
      height: widget.pickerTheme.pickerHeight,
      alignment: Alignment.center,
      child: Text("至",style: textStyle,),
    ));
    DateFormatter.splitDateFormat(widget.dateFormat);
    formatArr.forEach((format) {
      List<int> valueRange = _findPickerItemRangeRight(format);

      Widget pickerColumn = _renderDatePickerColumnComponent(
        scrollCtrl: _findScrollCtrlRight(format),
        valueRange: valueRange,
        format: format,
        valueChanged: (value) {
          if (format.contains('y')) {
            _changeYearSelectionRight(value);
          } else if (format.contains('M')) {
            _changeMonthSelectionRight(value);
          } else if (format.contains('d')) {
            _changeDaySelectionRight(value);
          }
        },
      );
      pickers.add(pickerColumn);
    });
    return Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween, children: pickers);
  }

  Widget _renderDatePickerColumnComponent({
    @required FixedExtentScrollController scrollCtrl,
    @required List<int> valueRange,
    @required String format,
    @required ValueChanged<int> valueChanged,
  }) {
    return Expanded(
      flex: 1,
      child: Container(
        padding: EdgeInsets.all(8.0),
        height: widget.pickerTheme.pickerHeight,
        decoration: BoxDecoration(color: widget.pickerTheme.backgroundColor),
        child: CupertinoPicker.builder(
          backgroundColor: widget.pickerTheme.backgroundColor,
          scrollController: scrollCtrl,
          itemExtent: widget.pickerTheme.itemHeight,
          onSelectedItemChanged: valueChanged,
          childCount: valueRange.last - valueRange.first + 1,
          itemBuilder: (context, index) =>
              _renderDatePickerItemComponent(valueRange.first + index, format),
        ),
      ),
    );
  }

  Widget _renderDatePickerItemComponent(int value, String format) {
    return Container(
      height: widget.pickerTheme.itemHeight,
      alignment: Alignment.center,
      child: Text(
        DateFormatter.formatDateTime(value, format),
        style:
        widget.pickerTheme.itemTextStyle ?? DATETIME_PICKER_ITEM_TEXT_STYLE,
      ),
    );
  }

  /// change the selection of year picker
  void _changeYearSelectionLeft(int index) {
    int year = _yearRangeLeft.first + index;
    if (_currYearLeft != year) {
      _currYearLeft = year;
      _changeDateRangeLeft();
      _onSelectedChange();
    }
  }
  /// change the selection of year picker
  void _changeYearSelectionRight(int index) {
    int year = _yearRangeRight.first + index;
    if (_currYearRight != year) {
      _currYearRight = year;
      _changeDateRangeRight();
      _onSelectedChange();
    }
  }

  /// change the selection of month picker
  void _changeMonthSelectionLeft(int index) {
    int month = _monthRangeLeft.first + index;
    if (_currMonthLeft != month) {
      _currMonthLeft = month;
      _changeDateRangeLeft();
      _onSelectedChange();
    }
  }

  /// change the selection of month picker
  void _changeMonthSelectionRight(int index) {
    int month = _monthRangeRight.first + index;
    if (_currMonthRight != month) {
      _currMonthRight = month;
      _changeDateRangeRight();
      _onSelectedChange();
    }
  }

  /// change the selection of day picker
  void _changeDaySelectionLeft(int index) {
    int dayOfMonth = _dayRangeLeft.first + index;
    if (_currDayLeft != dayOfMonth) {
      _currDayLeft = dayOfMonth;
      _onSelectedChange();
    }
  }

  /// change the selection of day picker
  void _changeDaySelectionRight(int index) {
    int dayOfMonth = _dayRangeRight.first + index;
    if (_currDayRight != dayOfMonth) {
      _currDayRight = dayOfMonth;
      _onSelectedChange();
    }
  }

  /// change range of month and day
  void _changeDateRangeLeft() {
    if (_isChangeDateRangeLeft) {
      return;
    }
    _isChangeDateRangeLeft = true;

    List<int> monthRange = _calcMonthRangeLeft();
    bool monthRangeChanged = _monthRangeLeft.first != monthRange.first ||
        _monthRangeLeft.last != monthRange.last;
    if (monthRangeChanged) {
      // selected year changed
      _currMonthLeft = max(min(_currMonthLeft, monthRange.last), monthRange.first);
    }

    List<int> dayRange = _calcDayRangeLeft();
    bool dayRangeChanged =
        _dayRangeLeft.first != dayRange.first || _dayRangeLeft.last != dayRange.last;
    if (dayRangeChanged) {
      // day range changed, need limit the value of selected day
      _currDayLeft = max(min(_currDayLeft, dayRange.last), dayRange.first);
    }

    setState(() {
      _monthRangeLeft = monthRange;
      _dayRangeLeft = dayRange;

      _valueRangeMapLeft['M'] = monthRange;
      _valueRangeMapLeft['d'] = dayRange;
    });

    if (monthRangeChanged) {
      // CupertinoPicker refresh data not working (https://github.com/flutter/flutter/issues/22999)
      int currMonth = _currMonthLeft;
      _monthScrollCtrlLeft.jumpToItem(monthRange.last - monthRange.first);
      if (currMonth < monthRange.last) {
        _monthScrollCtrlLeft.jumpToItem(currMonth - monthRange.first);
      }
    }

    if (dayRangeChanged) {
      // CupertinoPicker refresh data not working (https://github.com/flutter/flutter/issues/22999)
      int currDay = _currDayLeft;
      _dayScrollCtrlLeft.jumpToItem(dayRange.last - dayRange.first);
      if (currDay < dayRange.last) {
        _dayScrollCtrlLeft.jumpToItem(currDay - dayRange.first);
      }
    }

    _isChangeDateRangeLeft = false;
  }

  /// change range of month and day
  void _changeDateRangeRight() {
    if (_isChangeDateRangeRight) {
      return;
    }
    _isChangeDateRangeRight = true;

    List<int> monthRange = _calcMonthRangeRight();
    bool monthRangeChanged = _monthRangeRight.first != monthRange.first ||
        _monthRangeRight.last != monthRange.last;
    if (monthRangeChanged) {
      // selected year changed
      _currMonthRight = max(min(_currMonthRight, monthRange.last), monthRange.first);
    }

    List<int> dayRange = _calcDayRangeRight();
    bool dayRangeChanged =
        _dayRangeRight.first != dayRange.first || _dayRangeRight.last != dayRange.last;
    if (dayRangeChanged) {
      // day range changed, need limit the value of selected day
      _currDayRight = max(min(_currDayRight, dayRange.last), dayRange.first);
    }

    setState(() {
      _monthRangeRight = monthRange;
      _dayRangeRight = dayRange;

      _valueRangeMapRight['M'] = monthRange;
      _valueRangeMapRight['d'] = dayRange;
    });

    if (monthRangeChanged) {
      // CupertinoPicker refresh data not working (https://github.com/flutter/flutter/issues/22999)
      int currMonth = _currMonthRight;
      _monthScrollCtrlRight.jumpToItem(monthRange.last - monthRange.first);
      if (currMonth < monthRange.last) {
        _monthScrollCtrlRight.jumpToItem(currMonth - monthRange.first);
      }
    }

    if (dayRangeChanged) {
      // CupertinoPicker refresh data not working (https://github.com/flutter/flutter/issues/22999)
      int currDay = _currDayRight;
      _dayScrollCtrlRight.jumpToItem(dayRange.last - dayRange.first);
      if (currDay < dayRange.last) {
        _dayScrollCtrlRight.jumpToItem(currDay - dayRange.first);
      }
    }

    _isChangeDateRangeRight = false;
  }

  /// calculate the count of day in current month
  int _calcDayCountOfMonthLeft() {
    if (_currMonthLeft == 2) {
      return isLeapYear(_currYearLeft) ? 29 : 28;
    } else if (_solarMonthsOf31Days.contains(_currMonthLeft)) {
      return 31;
    }
    return 30;
  }
  /// calculate the count of day in current month
  int _calcDayCountOfMonthRight() {
    if (_currMonthRight == 2) {
      return isLeapYear(_currYearRight) ? 29 : 28;
    } else if (_solarMonthsOf31Days.contains(_currMonthRight)) {
      return 31;
    }
    return 30;
  }

  /// whether or not is leap year
  bool isLeapYear(int year) {
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
  }

  /// calculate selected index list
  List<int> _calcSelectIndexListLeft() {
    int yearIndex = _currYearLeft - _minDateTime.year;
    int monthIndex = _currMonthLeft - _monthRangeLeft.first;
    int dayIndex = _currDayLeft - _dayRangeLeft.first;
    return [yearIndex, monthIndex, dayIndex];
  }

  /// calculate selected index list
  List<int> _calcSelectIndexListRight() {
    int yearIndex = _currYearRight - _minDateTime.year;
    int monthIndex = _currMonthRight - _monthRangeRight.first;
    int dayIndex = _currDayRight - _dayRangeRight.first;
    return [yearIndex, monthIndex, dayIndex];
  }

  /// calculate the range of year
  List<int> _calcYearRange() {
    return [_minDateTime.year, _maxDateTime.year];
  }

  /// calculate the range of month
  List<int> _calcMonthRangeLeft() {
    int minMonth = 1, maxMonth = 12;
    int minYear = _minDateTime.year;
    int maxYear = _maxDateTime.year;
    if (minYear == _currYearLeft) {
      // selected minimum year, limit month range
      minMonth = _minDateTime.month;
    }
    if (maxYear == _currYearLeft) {
      // selected maximum year, limit month range
      maxMonth = _maxDateTime.month;
    }
    return [minMonth, maxMonth];
  }
  /// calculate the range of month
  List<int> _calcMonthRangeRight() {
    int minMonth = 1, maxMonth = 12;
    int minYear = _minDateTime.year;
    int maxYear = _maxDateTime.year;
    if (minYear == _currYearRight) {
      // selected minimum year, limit month range
      minMonth = _minDateTime.month;
    }
    if (maxYear == _currYearRight) {
      // selected maximum year, limit month range
      maxMonth = _maxDateTime.month;
    }
    return [minMonth, maxMonth];
  }

  /// calculate the range of day
  List<int> _calcDayRangeLeft({currMonth}) {
    int minDay = 1, maxDay = _calcDayCountOfMonthLeft();
    int minYear = _minDateTime.year;
    int maxYear = _maxDateTime.year;
    int minMonth = _minDateTime.month;
    int maxMonth = _maxDateTime.month;
    if (currMonth == null) {
      currMonth = _currMonthLeft;
    }
    if (minYear == _currYearLeft && minMonth == currMonth) {
      // selected minimum year and month, limit day range
      minDay = _minDateTime.day;
    }
    if (maxYear == _currYearLeft && maxMonth == currMonth) {
      // selected maximum year and month, limit day range
      maxDay = _maxDateTime.day;
    }
    return [minDay, maxDay];
  }
  /// calculate the range of day
  List<int> _calcDayRangeRight({currMonth}) {
    int minDay = 1, maxDay = _calcDayCountOfMonthRight();
    int minYear = _minDateTime.year;
    int maxYear = _maxDateTime.year;
    int minMonth = _minDateTime.month;
    int maxMonth = _maxDateTime.month;
    if (currMonth == null) {
      currMonth = _currMonthRight;
    }
    if (minYear == _currYearRight && minMonth == currMonth) {
      // selected minimum year and month, limit day range
      minDay = _minDateTime.day;
    }
    if (maxYear == _currYearRight && maxMonth == currMonth) {
      // selected maximum year and month, limit day range
      maxDay = _maxDateTime.day;
    }
    return [minDay, maxDay];
  }
}
